<template><div><h1 id="重构-改善既有代码设计-第二版" tabindex="-1"><a class="header-anchor" href="#重构-改善既有代码设计-第二版"><span>重构: 改善既有代码设计 - 第二版</span></a></h1>
<p>作者: Martin Fowler</p>
<p>从前，有位咨询顾问造访客户调研其开发项目。该系统的核心是一个类继承体系，顾问看了开发人员所写的一些代码。他发现整个体系相当凌乱，上层超类对系统的工作方式做了一些假设，下层子类实现这些假设。但是这些假设并不适合所有子类，导致覆写（override）工作非常繁重。只要在超类做点修改，就可以减少许多覆写工作。在另一些地方，超类的某些意图并未被良好理解，因此其中某些行为在子类内重复出现。还有一些地方，好几个子类做相同的事情，其实可以把它们搬到继承体系的上层去做。</p>
<p>这位顾问于是建议项目经理看看这些代码，把它们整理一下，但是项目经理并不热衷于此，毕竟程序看上去还可以运行，而且项目面临很大的进度压力。于是项目经理说，晚些时候再抽时间做这些整理工作。</p>
<p>顾问也把他的想法告诉了在这个继承体系上工作的程序员，告诉他们可能发生的事情。程序员都很敏锐，马上就看出问题的严重性。他们知道这并不全是他们的错，有时候的确需要借助外力才能发现问题。程序员立刻用了一两天的时间整理好这个继承体系，并删掉了其中一半代码，功能毫发无损。他们对此十分满意，而且发现在继承体系中加入新的类或使用系统中的其他类都更快、更容易了。</p>
<p>项目经理并不高兴。进度排得很紧，有许多工作要做。系统必须在几个月之后发布，而这些程序员却白白耗费了两天时间，做的工作与未来几个月要交付的大量功能毫不相干。原先的代码运行起来还算正常。的确，新的设计更加“纯粹”、更加“整洁”。但项目要交付给客户的，是可以有效运行的代码，不是用以取悦学究的代码。顾问接下来又建议应该在系统的其他核心部分进行这样的整理工作，这会使整个项目停顿一至两个星期。所有这些工作只是为了让代码看起来更漂亮，并不能给系统添加任何新功能。</p>
<p>你对这个故事有什么感想？你认为这个顾问的建议（更进一步整理程序）是对的吗？你会遵循那句古老的工程谚语吗：“如果它还可以运行，就不要动它。”</p>
<p>我必须承认自己有某些偏见，因为我就是那个顾问。6个月之后这个项目宣告失败，很大的原因是代码太复杂，无法调试，也无法将性能调优到可接受的水平。</p>
<p>后来，这个项目重新启动，几乎从头开始编写整个系统，Kent Beck受邀做了顾问。他做了几件迥异以往的事，其中最重要的一件就是坚持以持续不断的重构行为来整理代码。这个团队效能的提升，以及重构在其中扮演的角色，启发了我撰写本书的第1版，如此一来我就能够把Kent和其他一些人已经学会的“以重构方式改进软件质量”的知识，传播给所有读者。</p>
<p>自本书第1版问世至今，读者的反馈甚佳，重构的理念已经被广泛接纳，成为编程的词汇表中不可或缺的部分。然而，对于一本与编程相关的书而言，18年已经太漫长，因此我感到，是时候回头重新修订这本书了。我几乎重写了全书的每一页，但从其内涵而言，整本书又几乎没有改变。重构的精髓仍然一如既往，大部分关键的重构手法也大体不变。我希望这次修订能帮助更多的读者学会如何有效地进行重构。</p>
<h2 id="什么是重构" tabindex="-1"><a class="header-anchor" href="#什么是重构"><span>什么是重构</span></a></h2>
<p>所谓重构（refactoring）是这样一个过程：在不改变代码外在行为的前提下，对代码做出修改，以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法，可以最大限度地减小整理过程中引入错误的概率。本质上说，重构就是在代码写好之后改进它的设计。</p>
<p>“在代码写好之后改进它的设计”这种说法有点儿奇怪。在软件开发的大部分历史时期，大部分人相信应该先设计而后编码：首先得有一个良好的设计，然后才能开始编码。但是，随着时间流逝，人们不断修改代码，于是根据原先设计所得的系统，整体结构逐渐衰弱。代码质量慢慢沉沦，编码工作从严谨的工程堕落为胡砍乱劈的随性行为。</p>
<p>“重构”正好与此相反。哪怕手上有一个糟糕的设计，甚至是一堆混乱的代码，我们也可以借由重构将它加工成设计良好的代码。重构的每个步骤都很简单，甚至显得有些过于简单：只需要把某个字段从一个类移到另一个类，把某些代码从一个函数拉出来构成另一个函数，或是在继承体系中把某些代码推上推下就行了。但是，聚沙成塔，这些小小的修改累积起来就可以根本改善设计质量。这和一般常见的“软件会慢慢腐烂”的观点恰恰相反。</p>
<p>有了重构以后，工作的平衡点开始发生变化。我发现设计不是在一开始完成的，而是在整个开发过程中逐渐浮现出来。在系统构筑过程中，我学会了如何不断改进设计。这个“构筑-设计”的反复互动，可以让一个程序在开发过程中持续保有良好的设计。</p>
<h2 id="本书有什么" tabindex="-1"><a class="header-anchor" href="#本书有什么"><span>本书有什么</span></a></h2>
<p>本书是一本为专业程序员编写的重构指南。我的目的是告诉你如何以一种可控且高效的方式进行重构。你将学会如何有条不紊地改进程序结构，而且不会引入错误，这就是正确的重构方式。</p>
<p>按照传统，图书应该以概念介绍开头。尽管我也同意这个原则，但是我发现以概括性的讨论或定义来介绍重构，实在不是一件容易的事。因此，我决定用一个实例作为开路先锋。第1章展示了一个小程序，其中有些常见的设计缺陷，我把它重构得更容易理解和修改。其间你可以看到重构的过程，以及几个很有用的重构手法。如果你想知道重构到底是怎么回事，这一章不可不读。</p>
<p>第2章讨论重构的一般性原则、定义，以及进行重构的原因，我也大致介绍了重构面临的一些挑战。第3章由Kent Beck介绍如何嗅出代码中的“坏味道”，以及如何运用重构清除这些“坏味道”。测试在重构中扮演着非常重要的角色，第4章介绍如何在代码中构筑测试。</p>
<p>从第5章往后的篇幅就是本书的核心部分——重构名录。尽管不能说是一份巨细靡遗的列表，却足以覆盖大多数开发者可能用到的关键重构手法。这份重构名录的源头是20世纪90年代后期我开始学习重构时的笔记，直到今天我仍然不时查阅这些笔记，作为对我不甚可靠的记忆力的补充。每当我想做点什么——例如拆分阶段（154）——的时候，这份列表就会提醒我如何一步一步安全前进。我希望这是值得你日后一再回顾的部分。</p>
<h2 id="javascript代码范例" tabindex="-1"><a class="header-anchor" href="#javascript代码范例"><span>JavaScript代码范例</span></a></h2>
<p>与软件开发中的大多数技术性领域一样，代码范例对于概念的阐释至关重要。不过，即使在不同的编程语言中，重构手法看上去也是大同小异的。虽然会有一些值得留心的语言特性，但重构手法的核心要素都是一样的。</p>
<p>我选择了用JavaScript来展现本书中的重构手法，因为我感到大多数读者都能看懂这种语言。不过，即便你眼下正在使用的是别的编程语言，采用这些重构手法也应该不困难。我尽量不使用JavaScript任何复杂的特性，这样即便你对这门编程语言只有粗浅的了解，应该也能跟上重构的过程。另外，使用JavaScript展示重构手法，并不代表我推荐这门编程语言。</p>
<p>使用JavaScript展示代码范例，也不意味着本书介绍的技巧只适用于JavaScript。本书的第1版采用了Java，但很多从未写过任何Java代码的程序员也同样认为这些技巧很有用。我曾经尝试过用十多种不同的编程语言来呈现这些范例，以此展示重构手法的通用性，不过这对普通读者而言只会带来困惑。本书是为所有编程语言背景的程序员所作，除了阅读“范例”小节时需要一些基本的JavaScript知识，本书的其余部分都不特定于任何具体的编程语言。我希望读者能汲取本书的内容，并将其应用于自己日常使用的编程语言。具体而言，我希望读者能先理解本书中的JavaScript范例代码，然后再将其适配到自己习惯的编程语言。</p>
<p>因此，除了在特殊情况下，当我谈到“类”“模块”“函数”等词汇时，我都按照它们在程序设计领域的一般含义来使用这些词，而不是以其在JavaScript语言模型中的特殊含义来使用。</p>
<p>我只把JavaScript用作一种示例语言，因此我也会尽量避免使用其他程序员可能不太熟悉的编程风格。这不是一本“用JavaScript进行重构”的书，而是一本关于重构的通用书籍，只是采用了JavaScript作为示例。有很多JavaScript特有的重构手法很有意思（如将回调重构成promise或async/await），但这些不是本书要讨论的内容。</p>
<h2 id="谁该阅读本书" tabindex="-1"><a class="header-anchor" href="#谁该阅读本书"><span>谁该阅读本书</span></a></h2>
<p>本书的目标读者是专业程序员，也就是那些以编写软件为生的人。书中的范例和讨论，涉及大量需要详细阅读和理解的代码。这些例子都用JavaScript写成，不过这些重构手法应该适用于大部分编程语言。为了理解书中的内容，读者需要有一定的编程经验，但需要的知识并不多。</p>
<p>本书的首要目标读者群是想要学习重构的软件开发者，同时对于已经理解重构的人也有价值——本书可以作为一本教学辅助书。在本书中，我用了大量篇幅详细解释各个重构手法的过程和原理，因此有经验的开发人员可以用本书来指导同事。</p>
<p>尽管本书的关注对象是代码，但重构对于系统设计也有巨大影响。资深设计师和架构师也很有必要了解重构原理，并在自己的项目中运用重构技术。最好是由有威望的、经验丰富的开发人员来引入重构技术，因为这样的人最能够透彻理解重构背后的原理，并根据情况加以调整，使之适用于特定工作领域。如果你使用的不是JavaScript而是其他编程语言，这一点尤其重要，因为你必须把我给出的范例用其他编程语言改写。</p>
<p>下面我要告诉你，如何能够在不通读全书的情况下充分用好它。</p>
<ul>
<li>
<p>如果你想知道重构是什么 ，请阅读第1章，其中的示例会让你弄清楚重构的过程。</p>
</li>
<li>
<p>如果你想知道为什么应该重构 ，请阅读前两章，它们会告诉你重构是什么以及为什么应该重构。</p>
</li>
<li>
<p>如果你想知道该在什么地方重构 ，请阅读第3章，它会告诉你一些代码特征，这些特征指出“这里需要重构”。</p>
</li>
<li>
<p>如果你想着手进行重构 ，请完整阅读前四章，然后选择性地阅读重构名录。一开始只需概略浏览列表，看看其中有些什么，不必理解所有细节。一旦真正需要实施某个重构手法，再详细阅读它，从中获取帮助。列表部分是供查阅的参考性内容，你不必一次就把它全部读完。</p>
</li>
</ul>
<p>给形形色色的重构手法命名是编写本书的重要部分。合适的词汇能帮助我们彼此沟通。当一名开发者向另一名开发者提出建议，将一段代码提取成为一个函数，或者将计算逻辑拆分成几个阶段，双方都能理解提炼函数（106）和拆分阶段（154）是什么意思。这份词汇表也能帮助开发者选择自动化的重构手法。</p>
<h2 id="站在前人的肩膀上" tabindex="-1"><a class="header-anchor" href="#站在前人的肩膀上"><span>站在前人的肩膀上</span></a></h2>
<p>就在本书一开始的此时此刻，我必须说：这本书让我欠了一大笔人情债，欠那些在20世纪90年代做了大量研究工作并开创重构领域的人一大笔债。学习他们的经验启发了我撰写本书第1版，尽管已经过去了很多年，我仍然必须感谢他们打下的基础。这本书原本应该由他们之中的某个人来写，但最后却让我这个有时间、有精力的人捡了便宜。</p>
<p>重构技术的两位最早倡导者是 Ward Cunningham 和Kent Beck。他们很早就把重构作为软件开发过程的一块基石，并且在自己的开发过程中运用它。尤其需要说明的是，正因为和Kent合作，我才真正看到了重构的重要性，并直接受到激励写了这本书。</p>
<p>Ralph Johnson在UIUC（伊利诺伊大学厄巴纳-香槟分校）领导了一个小组，这个小组因其在对象技术方面的实用贡献而声名远扬。Ralph很早就是重构的拥护者，他的一些学生也在重构领域的发展前期做出重要研究。Bill Opdyke的博士论文是重构研究的第一份详细的书面成果。John Brant和Don Roberts则早已不满足于写文章了，他们创造了第一个自动化的重构工具，这个叫作Refactoring Browser（重构浏览器）的工具可以用于重构Smalltalk程序。</p>
<p>自本书第1版问世以来，很多人推动了重构领域的发展。尤其是，开发工具中的自动化重构功能，让程序员的生活轻松了许多。如今我只要简单地敲几下键盘就可以给一个被大量使用的函数改名，对此我已经习以为常，但在这快捷的操作背后，离不开IDE开发团队的辛勤劳动。</p>
<h2 id="致谢" tabindex="-1"><a class="header-anchor" href="#致谢"><span>致谢</span></a></h2>
<p>尽管有这些研究成果可以借鉴，我还是需要很多协助才能写成本书。本书的第1版极大地得益于Kent Beck的经验与鼓励。起初向我介绍重构的是他，鼓励我开始书面记录重构手法的是他，帮助我把重构手法组织成型的也是他，提出“代码味道”这个概念的还是他。我常常感觉，他本可以把本书的第1版写得更好——如果当时他不是在忙着撰写极限编程的奠基之作《解析极限编程》的话。</p>
<p>我认识的所有技术图书作者都会提到，技术审稿人提供了巨大的帮助。我们的作品都会有巨大的缺陷，只有同行审稿人能发现这些缺陷。我自己并不常做技术审稿，部分原因是我认为自己并不擅长，所以我对优秀的技术审稿人总是满怀敬意。帮别人审稿所得的报酬微不足道，所以这完全是一项慷慨之举。</p>
<p>正式开始写这本书时，我建了一个邮件列表，其中都是能给我提供反馈的建议者。随着写作的进展，我不断把新的草稿发到这个小组里，请他们给我反馈。我要感谢这些人在邮件列表中提供的反馈：Arlo Belshee、Avdi Grimm、Beth Anders-Beck、Bill Wake、Brian Guthrie、Brian Marick、Chad Wathington、Dave Farley、David Rice、Don Roberts、Fred George、Giles Alexander、Greg Doench、Hugo Corbucci、Ivan Moore、James Shore、Jay Fields、Jessica Kerr、Joshua Kerievsky、Kevlin Henney、Luciano Ramalho、Marcos Brizeno、Michael Feathers、Patrick Kua、Pete Hodgson、Rebecca Parsons和Trisha Gee。</p>
<p>在这群人中，我要特别感谢Beth Anders-Beck、James Shore和Pete Hodgson在JavaScript方面给我的帮助。</p>
<p>有了一个比较完整的初稿之后，我将它发送出去，寻求更多的审阅意见，因为我希望有一些全新的眼光来纵览全书。William Chargin和Michael Hunger提供了极其详尽的审阅意见。我还从Bob Martin和Scott Davis那里得到了很多有用的意见。Bill Wake也对本书初稿做了完整的审阅，并在邮件列表中给出了他的意见。</p>
<p>我在ThoughtWorks的同事一直给我的写作提供想法和反馈。数不胜数的问题、评论和观点推动了本书的思考与写作。作为ThoughtWorks员工最好的一件事，就是这家公司允许我花大量时间来写作。我尤其要感谢Rebecca Parsons（我们的CTO）经常与我交流，给了我很多想法。</p>
<p>在培生出版集团，Greg Doench是负责本书的策划编辑，他解决了无数的问题，最终使本书得以出版；Julie Nahil是责任编辑；Dmitry Kirsanov负责文字编辑工作；Alina Kirsanova负责排版和制作索引。我也很高兴与他们合作。</p>
</div></template>


